/*
 *Copyright 2021-2023 NERCIS
 *
 *Licensed under the Apache License, Version 2.0 (the "License");
 *you may not use this file except in compliance with the License.
 *You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 *Unless required by applicable law or agreed to in writing, software
 *distributed under the License is distributed on an "AS IS" BASIS,
 *WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *See the License for the specific language governing permissions and
 *limitations under the License.
 */

package cn.ac.nercis.pes.service.project.imported;

import cn.ac.nercis.pes.common.constant.common.BaseConstants;
import cn.ac.nercis.pes.model.dto.common.Result;
import cn.ac.nercis.pes.model.dto.project.ImportConflictDTO;
import cn.ac.nercis.pes.model.standard.analyze.UnitRiskAnalysisSDTO;
import cn.ac.nercis.pes.repository.dal.evaluation.AssetKeyDataRepository;
import cn.ac.nercis.pes.repository.dal.evaluation.AssetRepository;
import cn.ac.nercis.pes.repository.dal.evaluation.UnitRiskAnalysisRepository;
import cn.ac.nercis.pes.repository.dal.project.SystemTargetRepository;
import cn.ac.nercis.pes.repository.model.evaluation.UnitRiskAnalysisDO;
import cn.ac.nercis.pes.repository.model.project.SystemTargetDO;
import cn.ac.nercis.pes.service.utils.CheckFieldUtils;
import lombok.NonNull;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.time.LocalDateTime;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.stream.Collectors;

/**
 * 风险分析信息导入服务
 *
 * @author zpy
 * @version 1.0.0
 */
@Slf4j
@Service
public class RiskAnalysisImportService extends CommonImportService {
    private final UnitRiskAnalysisRepository riskAnalysisRepository;
    private final AssetRepository assetRepository;
    private final AssetKeyDataRepository assetKeyDataRepository;
    private final SystemTargetRepository systemTargetRepository;

    @Autowired
    public RiskAnalysisImportService(UnitRiskAnalysisRepository riskAnalysisRepository,
                                     AssetRepository assetRepository,
                                     AssetKeyDataRepository assetKeyDataRepository,
                                     SystemTargetRepository systemTargetRepository) {
        this.riskAnalysisRepository = riskAnalysisRepository;
        this.assetRepository = assetRepository;
        this.assetKeyDataRepository = assetKeyDataRepository;
        this.systemTargetRepository = systemTargetRepository;
    }

    public Result<List<ImportConflictDTO>> checkRiskAnalysis(@NonNull String systemId,
                                                             List<UnitRiskAnalysisSDTO> riskAnalysis) {
        var sourceData = this.riskAnalysisRepository.findAllBySystemId(systemId);
        if (CollectionUtils.isEmpty(sourceData)) {
            return Result.success();
        }
        var systemTargets = systemTargetRepository.findAllBySystemId(systemId);
        var conflictData = riskAnalysis.stream()
                .flatMap(ec -> this.checkRiskAnalysis(ec, sourceData, systemTargets).stream())
                .filter(Objects::nonNull)
                .collect(Collectors.toList());
        return Result.success(conflictData);
    }

    public Result<String> importRiskAnalysis(@NonNull String systemId,List<UnitRiskAnalysisSDTO> riskAnalysis, Boolean cover) {
        var sourceData = this.riskAnalysisRepository.findAllBySystemId(systemId);
        Result<String> result = Result.success();
        for (var item : riskAnalysis) {
            result = this.importRiskAnalysis(systemId, item, sourceData, cover);
            if (!result.isSuccess()) {
                break;
            }
        }
        return result;
    }

    private List<ImportConflictDTO> checkRiskAnalysis(@NonNull UnitRiskAnalysisSDTO importData,
                                                      List<UnitRiskAnalysisDO> sourceData,
                                                      List<SystemTargetDO> systemTargets) {
        var entity = sourceData.stream()
                .filter(st -> StringUtils.equals(st.getLayerTargetId(), importData.getLayerTargetId())
                        && StringUtils.equals(st.getUnitTargetId(), importData.getUnitTargetId()))
                .findFirst()
                .orElse(null);
        if (Objects.isNull(entity)) {
            return Collections.emptyList();
        }
        List<ImportConflictDTO> conflictData = CheckFieldUtils.checkField(entity, importData);
        if (importData.getRiskLevel() != entity.getRiskLevel()) {
            conflictData.add(ImportConflictDTO.builder()
                    .fieldName("风险等级")
                    .oldContent(entity.getRiskLevel().getName())
                    .newContent(importData.getRiskLevel().getName())
                    .build());
        }
        if (CollectionUtils.isNotEmpty(conflictData)) {
            String title = getTargetName(importData.getUnitTargetId(), systemTargets);
            conflictData.add(0, ImportConflictDTO.builder()
                    .fieldName(BaseConstants.IMPORT_CONFLICT_DATA_TAG)
                    .newContent(title)
                    .oldContent(title)
                    .build());
        }
        return conflictData;
    }

    private Result<String> importRiskAnalysis(String systemId, @NonNull UnitRiskAnalysisSDTO importData,
                                              List<UnitRiskAnalysisDO> sourceData, Boolean cover) {
        var entity = sourceData.stream()
                .filter(st -> StringUtils.equals(st.getLayerTargetId(), importData.getLayerTargetId())
                        && StringUtils.equals(st.getUnitTargetId(), importData.getUnitTargetId()))
                .findFirst().orElse(null);
        if (Objects.isNull(entity)) {
            return createRiskAnalysis(systemId, importData,sourceData);
        } else {
            return modifyRiskAnalysis(entity, importData, cover);
        }
    }

    private Result<String> createRiskAnalysis(String systemId, UnitRiskAnalysisSDTO importData,
                                              List<UnitRiskAnalysisDO> sourceData) {
        var entity = UnitRiskAnalysisDO.builder()
                .systemId(systemId)
                .layerTargetId(importData.getLayerTargetId())
                .unitTargetId(importData.getUnitTargetId())
                .problem(importData.getProblem())
                .suggest(importData.getSuggest())
                .analysis(importData.getAnalysis())
                .threatCode(importData.getThreatCode())
                .riskLevel(importData.getRiskLevel())
                .build();
        entity.setCreateTime(LocalDateTime.now());
        return Optional.of(entity)
                .map(this.riskAnalysisRepository::save)
                .map(data->{
                    sourceData.add(data);
                    return Result.success(data.getId());
                })
                .orElse(Result.failed("导入风险分析信息，创建失败"));
    }

    private Result<String> modifyRiskAnalysis(UnitRiskAnalysisDO sourceData, UnitRiskAnalysisSDTO importData, Boolean cover) {
        sourceData.setProblem(CheckFieldUtils.applyContent(sourceData.getProblem(), importData.getProblem(), cover));
        sourceData.setSuggest(CheckFieldUtils.applyContent(sourceData.getSuggest(), importData.getSuggest(), cover));
        sourceData.setAnalysis(CheckFieldUtils.applyContent(sourceData.getAnalysis(), importData.getAnalysis(), cover));
        sourceData.setThreatCode(CheckFieldUtils.applyContent(sourceData.getThreatCode(), importData.getThreatCode(), cover));
        sourceData.setRiskLevel(CheckFieldUtils.applyContent(sourceData.getRiskLevel(), importData.getRiskLevel(), cover));
        sourceData.setModifyTime(LocalDateTime.now());
        return Optional.of(sourceData)
                .map(this.riskAnalysisRepository::save)
                .map(UnitRiskAnalysisDO::getId)
                .map(Result::success)
                .orElse(Result.failed("导入风险分析信息，修改失败"));
    }
}
